package com.hup.utils.commons.start;

import com.hup.utils.commons.TextUtil;
import org.apache.commons.cli.*;
import org.apache.commons.cli.Option.Builder;

import java.lang.annotation.*;
import java.lang.reflect.Field;


/**
 * 命令行入参指令处理类; {@link #handleArgs(Class, String[])}
 * 需要加入依赖:
 *  <dependency>
 *      <groupId>commons-cli</groupId>
 *      <artifactId>commons-cli</artifactId>
 *      <version>1.4</version>
 *  </dependency>
 *
 * 附,计划任务启动jar的方式:
 * .[程序或脚本]:jar路径
 * .[添加参数]:命令行入参指令
 * .[起始于]:jar父目录(不设置并以管理员启动时,会影响'./'的位置)
 *
 * @author hugan
 * @apiNote 本类不加入日志记录代码, 所以在启动成功前(入参错误, 输入help), 不会生成日志文件
 * @date 2020/11/06
 */
//@Log4j2//test
public class CliHandler {
    private static final String CMD_HELP = "help";

    /**
     * 处理命令行入参:
     * .预设[help,h]帮助命令:打印入参说明
     * .不允许未知入参,入参异常时,打印入参说明并退出程序
     * .入参正常时,将入参转化为对象
     *
     * @param args 命令行入参
     * @param clz notNull,入参转化后的类;
     * @return notNull, 有返回时都是非空对象;否则都是本方法exit
     * @apiNote 本方法应该用在main方法的第一行中
     */
    public static <T> T handleArgs(Class<T> clz, String[] args) {
        if (clz == null) throw new IllegalArgumentException("入参类不能为空");
        Options initOptions = initOptions(clz);
        try {
            CommandLine parse = new DefaultParser().parse(initOptions, args);
            //printHelpAndExit(initOptions);
            if (parse.getOptions().length == 0) {
                //没有入参
                return clz.newInstance();
            }

            if (parse.hasOption(CMD_HELP)) {
                printHelpAndExit(initOptions);
                //这里已经exit了
            }

            /*
            通过反射,将命令行入参转成对象属性
            目前只支持boolean
             */
            T res = clz.newInstance();
            for (Field field : clz.getDeclaredFields()) {
                CliOption cliOption = field.getAnnotation(CliOption.class);
                if (cliOption == null) continue;

                field.setAccessible(true);
                boolean hasOption = parse.hasOption(cliOption.paramShort());
                //log.info("field={}, ={}", field.getName(), hasOption);
                if (hasOption) field.set(res, true);
            }
            return res;
        } catch (UnrecognizedOptionException e) {
            //给控制台输出的信息
            System.out.println("入参异常:" + e.getOption());
        } catch (Exception e) {
            System.out.println("参数处理异常:" + e.getMessage());
        }
        printHelpAndExit(initOptions);
        return null;//return不会执行,help前已exit了
    }

    /**
     * 创建命令行入参数据:内置帮助命令,加上 clz的命令
     *
     * @param clz 入参数据转化类,需要对象属性中含 {@link CliOption}
     */
    private static Options initOptions(Class<?> clz) {
        Options options = new Options();
        //内置帮助命令
        options.addOption(Option.builder("h").longOpt(CMD_HELP).desc("帮助").build());

        //遍历本类的变量(包括公共、受保护、默认（包）访问和专用字段，但不包括继承的字段)
        for (Field field : clz.getDeclaredFields()) {
            CliOption cliOption = field.getAnnotation(CliOption.class);
            if (cliOption == null) continue;
            //log.info("field={}", field.getName());
            Builder builder = Option.builder(cliOption.paramShort()).desc(cliOption.desc());
            if (!TextUtil.isBlank(cliOption.paramLong())) builder.longOpt(cliOption.paramLong());
            options.addOption(builder.build());
        }
        return options;
    }

    private static void printHelpAndExit(Options options) {
        HelpFormatter helpFormatter = new HelpFormatter();
        helpFormatter.setSyntaxPrefix("用法: ");
        helpFormatter.printHelp("<appName>", options, true);
        System.exit(0);
    }

    /**
     * 命令行参数注解类;
     *
     * @apiNote 目前只支持boolean变量
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    public @interface CliOption {

        /**
         * 短入参(也可能不是单字母缩写),如"h","boot"
         */
        String paramShort();

        /**
         * 长入参,如"help"
         */
        String paramLong() default "";

        /**
         * 入参说明,如"帮助"
         */
        String desc();

    }

}
